# -*- coding: utf-8 -*-

"""This module contains a concrete implementation of AuthBase (an
authentication class), as well as conditions (predicates) for
authorization.

The conditions follow Arnar Birgisson's style at
http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
"""

from __future__ import (absolute_import, division, print_function,
    unicode_literals)
from sqlalchemy import desc
from sqlalchemy.exc import InvalidRequestError
from cherrypy  import config, request, response

from .. import dir_wp, flash, redirect, url, ShowUserThisException
from ..gensher import gensher, page
from ..formetto import Formetto
from ..i18n import gettext as _

from .base import AuthBase as _AuthBase
from .model import User, login_is_email, session


def require(*conditions):
    """A decorator that allows function execution only if the user
    satisfies some conditions (which are callable predicates).

    Else, redirects to the login page, with an appropriate message.
    """
    def decorate(func):
        def wrap(*a, **kw):
            for condition in conditions:
                # A condition is a callable that returns True or False
                if not condition(): request.app.auth.authorization_fail()
            return func(*a, **kw)
        return wrap
    return decorate

# We define this condition here only because it is going to be used in the
# class below.
def has_permission(p):
    def predicate():
        auth = request.app.auth
        return auth.current_login is not None and \
               p in (p.name for p in auth.current_user.permissions)
    return predicate



class AuthController(_AuthBase):
    """Adapts AuthBase so it works with our Genshi templates and SQLAlchemy."""
    def __init__(self, app, prefix="/auth/"):
        """Sets up Auth routes, and more."""
        connect = app.dispatch.connect
        rts = "login|authenticate|logout|register|save|report"
        connect("Auth", route=prefix+":action", controller=self,
                requirements=dict(action=rts))
        if url: # Next statement does not run in interpreter mode
            self.login_url = url("Auth", action="login")
            # TODO: Find out why url==None when env==interpreter
        # Put this AuthController instance in the default dictionary, so all
        # templates will have access to authentication information.
        app.defaultdict["auth"] = self
        app.auth = self
        # All templates for this controller are in this directory
        app.gensher.add_search_path(dir_wp + "/auth/templates")


    # Authentication

    def get_user(self, login):
        """Required override. Returns the User object."""
        try:
            return User.get_by_login(login)
        except InvalidRequestError, e:
            return _("Sorry, that login does not exist. Try again.")

    @page("login.html")
    def login(self):
        adict = super(AuthController, self).login_page()
        if request.app.config["/"].get("login_is_email", True):
            adict["loginlabel"] = u"E-mail:"
        else:
            adict["loginlabel"] = u"Login:"
        return adict

    # Configuration for the "register" page; can be easily modified by the app
    reqfields_msg = u"Campos obrigatórios indicados com um * asterisco."
    hide_address = False

    @page("register.html")
    def register(self):
        """Shows a form for the user to sign up."""
        if self.current_login:
            o = self.current_user
            if isinstance(o, basestring):  raise ShowUserThisException(o)
        else:    # If new user, create a blank form
            o = User.new_empty()
            #o.expunge()   # make it transient, so it won't be persisted
        # print(o, self.current_login)
        return dict(
            f = Formetto(o),
            pagetitle = u"Registro",
            reqfields_msg = self.reqfields_msg,
            hide_address = self.hide_address,
        )

    def save(self, **k):
        k["newsletter"] = k.get("newsletter", None) == "on"
        id = k.pop("id", None)
        try:
            o, is_new = User.put(id, k, flush=True)
        except ShowUserThisException, e:
            return gensher.error(e)
        flash(_(u"Obrigado. Seus dados foram salvos."))
        # Because the user may have changed their e-mail or name:
        self.authenticate_directly(o)
        self.on_saved()

    def on_saved(self):
        """When the user is saved, by default the form is redisplayed with a
        success message. To go some page else, the application can substitute
        this method.
        """
        raise redirect("Auth", action="register")

    @require(has_permission("Admin"))
    @page("report.html")
    def report(self):
        """Shows a report of all users."""
        headings = (u"Newsletter", u"E-mail", u"Nome", u"Profissão", "Empresa",
                    u"Telefones", u"Rua", u"Bairro", u"Cidade", u"Estado", u"País",
                    u"CEP", u'Criado', u'Modificado')
        fields = ("newsletter", "email", "display_name", "profession",
                  "organization", "phones", "street", "district", "city",
                  "province", "country", "zipcode", 'created', 'changed')
        # Most recently changed users shall appear at the top
        #objects = User.select()
        objects = session.query(User).order_by(desc(User.changed)).all()
        return dict(
            pagetitle = u"Usuários do website",
            pagecontent = gensher.fragment("report_table.html",
                dict(headings=headings, fields=fields, objects=objects)),
        )


    login_is_email = staticmethod(login_is_email)


    # Authorization

    # Conditions are callables that return True or False.
    # They can access the current username as request.login
    # Define those at will, however suits the application.

    @staticmethod
    def in_group(groupname):
        def check():
            auth = request.app.auth
            return auth.current_login is not None and \
                   groupname in (g.name for g in auth.current_user.groups)
        return check

    @staticmethod
    def login_is(s):
        return lambda: s == request.app.auth.current_login

    has_permission = staticmethod(has_permission)

    @staticmethod
    def not_anonymous():
        return lambda: request.app.auth.current_login is not None

    # Meta conditions

    @staticmethod
    def any_of(*conditions):
        """Returns True if any of the conditions match"""
        def check():
            for c in conditions:
                if c():
                    return True
            return False
        return check

    @staticmethod
    def all_of(*conditions):
        """Returns True if all passed conditions match.
        By default all conditions are required, but this might still be
        needed if you want to use it inside of an any_of(...) condition.
        """
        def check():
            for c in conditions:
                if not c():
                    return False
            return True
        return check




# TODO: I don't like this solution. Need a better API, separating static stuff.
class auth(object):
    """A proxy to make it easy to use AuthController methods and properties
    without having to write "request.app.auth" every time.
    """
    def __getattr__(self, attrib):
        if attrib in ("current_user", "current_login"):  # get these from
            return getattr(request.app.auth, attrib)     # the instance,
        return getattr(AuthController, attrib) # and the rest from the class.
auth = auth()
